-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add generic trait to combine UnixListener and TcpListener #4385
Conversation
tokio-util/src/net/unix/mod.rs
Outdated
#[derive(Debug)] | ||
pub enum ListenAddr { | ||
/// Socket address. | ||
SocketAddr(std::net::SocketAddr), | ||
/// Unix socket. | ||
UnixSocket(tokio::net::unix::SocketAddr), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be an associated type of the Listener
trait like how Io
is set up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 9bda024
tokio-util/src/net/unix/mod.rs
Outdated
/// A trait for a listener: `TcpListener` and `UnixListener`. | ||
pub trait Listener: Send + Unpin { | ||
/// The stream's type of this listener. | ||
type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite; | ||
|
||
/// Accepts a new incoming connection from this listener. | ||
fn accept<'a>( | ||
&'a self, | ||
) -> Pin<Box<dyn Future<Output = Result<(Self::Io, ListenAddr)>> + Send + 'a>>; | ||
|
||
/// Returns the local address that this listener is bound to. | ||
fn local_addr(&self) -> Result<ListenAddr>; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the trait should look more like this:
/// A trait for a listener: `TcpListener` and `UnixListener`. | |
pub trait Listener: Send + Unpin { | |
/// The stream's type of this listener. | |
type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite; | |
/// Accepts a new incoming connection from this listener. | |
fn accept<'a>( | |
&'a self, | |
) -> Pin<Box<dyn Future<Output = Result<(Self::Io, ListenAddr)>> + Send + 'a>>; | |
/// Returns the local address that this listener is bound to. | |
fn local_addr(&self) -> Result<ListenAddr>; | |
} | |
/// A trait for a listener: `TcpListener` and `UnixListener`. | |
pub trait Listener { | |
/// The stream's type of this listener. | |
type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite; | |
/// The address of the new connection. | |
type Addr; | |
fn poll_accept(self: Pin<&mut self>) -> Poll<(Self::Io, Self::ListenAddr)>; | |
/// Returns the local address that this listener is bound to. | |
fn local_addr(&self) -> Result<ListenAddr>; | |
/// Accepts a new incoming connection from this listener. | |
fn accept<'a>(&'a mut self) -> ListenerAcceptFut<'a, Self> | |
where | |
Self: Sized + Unpin, | |
{ | |
ListenerAcceptFut::new(self) | |
} | |
} |
Then, the crate would define a ListenAcceptFut
type that implements Future
with a manual impl similar to the return types in the Ext
traits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in c2ec467
I'm trying out with the current API using type alias but it seems it doesn't work with my use case because the TCP address/Unix socket path is provided at runtime so I would need to dynamic dispatch somewhere. |
You can implement the trait for the |
Oh I think I see! Thanks! |
@Darksonn ok I think I'm ready for review |
(Fixed clippy stuff) |
tokio-util/src/net/mod.rs
Outdated
/// Polls to accept a new incoming connection to this listener. | ||
fn poll_accept(&self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>; | ||
|
||
/// Accepts a new incoming connection from this listener. | ||
fn accept(&self) -> ListenerAcceptFut<'_, Self> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's best to accept a &mut self
here. Calling accept
twice in parallel will result in one of them breaking due to lost wakeups.
/// Polls to accept a new incoming connection to this listener. | |
fn poll_accept(&self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>; | |
/// Accepts a new incoming connection from this listener. | |
fn accept(&self) -> ListenerAcceptFut<'_, Self> | |
/// Polls to accept a new incoming connection to this listener. | |
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>; | |
/// Accepts a new incoming connection from this listener. | |
fn accept(&mut self) -> ListenerAcceptFut<'_, Self> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would make the API different from tokio's TCP listener and Unix listener. If they don't use &mut self there, why would I need it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The accept
fn in Tokio will not have the same lost wakeup issue as this one does because it isn't implemented by calling poll_accept
on itself. However, we cant really replicate what Tokio does with a trait. The &mut self
avoids the footgun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I see!! Thanks for the explanation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in fe231ec
tokio-util/src/net/mod.rs
Outdated
/// Polls to accept a new incoming connection to this listener. | ||
fn poll_accept(&self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should take a pinned reference to self.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replicated the signature of the function from tokio's TCP listener. Are you sure you want to make it different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, making this take &self
will make it difficult to implement this trait for anything else than the two listener types in Tokio itself. This is why I suggested changing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I see
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in fe231ec
tokio-util/src/net/unix/mod.rs
Outdated
use super::Listener; | ||
use std::io::Result; | ||
use std::task::{Context, Poll}; | ||
|
||
impl Listener for tokio::net::UnixListener { | ||
type Io = tokio::net::UnixStream; | ||
type Addr = tokio::net::unix::SocketAddr; | ||
|
||
fn poll_accept(&self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>> { | ||
Self::poll_accept(self, cx) | ||
} | ||
|
||
fn local_addr(&self) -> Result<Self::Addr> { | ||
self.local_addr().map(Into::into) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just put this in the other folder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will force me to add #[cfg(unix)]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I don't mind whether it is separate or together then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I'll keep it then but I don't mind either way either
Co-authored-by: Alice Ryhl <[email protected]>
I think this is ready for review again. |
Your branch is 59 commits behind tokio-rs:master and is therefore missing a fix for the CI failure in the two FreeBSD CI runs. Please merge master into your branch. |
tokio-util/src/net/mod.rs
Outdated
/// Polls to accept a new incoming connection to this listener. | ||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>; | ||
|
||
/// Accepts a new incoming connection from this listener. | ||
fn accept(&mut self) -> ListenerAcceptFut<'_, Self> | ||
where | ||
Self: Sized + Unpin, | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either poll_accept
should take self: Pin<&mut Self>
, or the Unpin
bound should be removed from accept
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in a538f0b
oops 😅 I didn't notice somehow, sorry! Fixed in 812dfa5 And ready for review. |
tokio-util/src/net/mod.rs
Outdated
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>> { | ||
match self { | ||
Either::Left(listener) => listener | ||
.poll_accept(cx) | ||
.map(|res| res.map(|(stream, addr)| (Either::Left(stream), Either::Left(addr)))), | ||
Either::Right(listener) => listener | ||
.poll_accept(cx) | ||
.map(|res| res.map(|(stream, addr)| (Either::Right(stream), Either::Right(addr)))), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. It's unfortunate that the types here allow it return stuff like (Left(io), Right(addr))
even though it wont in practice. Ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow I'm so blind I didn't even notice.
Yes you're right in practice this will never happen. If someones use match
on the Result, they will need to make unreachable branches just for that. It's stupid.
.... 🤔 I will think about it... but I don't see a solution right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably making yet another Either
specialized for Listener could solve the issue.
I noticed the Either
type is something not universal anyway: tokio-util has one but tower also has its own: https://docs.rs/tower/latest/tower/util/enum.Either.html
I assume we could have a "EitherListener
" with a poll_accept
method that would return Result<Either<(IoLeft, AddrLeft), (IoRight, AddrRight)>>
. The enum itself would look like this I guess:
pub enum EitherListener<L: Listener, R: Listener> {
Left(L),
Right(R),
}
I don't think the user absolutely "needs" to use tokio-util's Either
, it could definitely be something more specialized. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced that adding an EitherListener
enum changes anything here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EitherListener
would not implement Listener
, it would have its own method with their own signatures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is to change the trait so that Addr and Io are merged into a single type which each listener will define as a pair.
That would work too but not super elegant I guess...
You could just put the method on the Either we already have.
Good point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I replaced the Listener impl on Either by custom methods here: b55b62c3
Unfortunately the accept Future struct is slightly different so I had to duplicate some code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just make it an async fn and drop the poll function? We don't need these tricks when it's not a trait method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 316f015
Co-authored-by: Alice Ryhl <[email protected]>
Thank you for your time |
Thank you for the PR! |
Motivation
I would like to have a generic type for creating a socket from TCP or from a Unix socket so I don't need to duplicate code.
Solution
Added a new type that combines the SocketAddr of TcpListener and the one of UnixListener. I called it ListenAddr.[edit: opted for associated type]accept()
method and alocal_addr()
method to reflect the methods from TcpListener and UnixListener. [edit: alsoaccept_poll()
]Added a new trait AsyncReadWrite which combines AsyncRead and AsyncWrite. This is because I will need to box the stream.[edit: opted for associated type]Added a method[edit: removed because it can be handled by the user easily]set_nodelay()
to reflect the methodset_nodelay()
of TcpStream which I needed.Problem encountered
There is a module "udp" at root of tokio-util. It should probably moved to the new module "net" I created. One alternative would be to rename the "unix" module I created to something else.
Follow up of #4244